Un service, comme son nom l’indique est un objet fournissant un service au reste de l’application. Par exemple, le composant FormBuilder
fournit un service simple pour générer un formulaire HTML à partir d’une déclaration PHP. Mais FormBuilder
n’est pas dépendant d’autres objets, ni dans sa construction ni dans son utilisation.
Mais imaginons un instant que FormBuilder
choisisse d’utiliser le composant Template
pour la génération des inputs.
Il faudrait qu’à chaque déclaration d’un FormBuilder
un objet Template
soit fourni :
$form = new FormBuilder(new Template());
Ici, nous illustrons une hypothèse simple pour représenter la dépendance, mais l’exemple reste élémentaire. Imaginons cette fois-ci que nous déterminions que l’objet Template
a besoin d’un autre objet pour fonctionner, cela nous donnerait :
$form = new FormBuilder(new Template(new ObjetForTemplate()));
Vous conviendrez que l’utilisation de notre formulaire va poser problème, puisque qu’il dépend d’un objet dépendant lui-même d’un autre objet. C’est là qu’intervient le Container.
J’utilise l’appellation Container pour simplifier sa désignation, mais en réalité il s’agit d’un Conteneur d’injection de dépendances ou CID. En algorithmie, il existe un design pattern appelé l’injection de dépendance qui vise à résoudre la contrainte des dépendances entre les objets.
Le Container utilise ce design pattern pour définir un registre d’objets et les injecter à ceux qui en sont dépendants. SoosyzeFramework utilise la recommandation PSR-11: Container interface fournissant les méthodes suivantes pour utiliser un service :
/* La fonction get() retourne le service 'myService'. */
/* En cas d’absence du service, une exception est levée. */
$myService = $container->get('myService');
/* La fonction has() retourne TRUE si le service 'myService' existe, sinon FALSE. */
$container->has('myService');
SoosyzeFramework fournit l’instance du Container à chaque contrôleur, vous permettant d’utiliser ses services dans chacun de vos modules. De plus, il fournit 3 services de bases :
core
pour utiliser les méthodes du cœur de votre application,config
pour récupérer des valeurs dans les fichiers de configuration,router
pour la manipulation des routes.Pour appeler un service dans un contrôleur, plusieurs méthodes sont possibles :
/* Directement avec l’attribut de classe $container. */
$this->container->get('router');
/* Ou avec la fonction get() du contrôleur. */
$this->get('router');
/* Ou de façon statique. */
self::router();
Personnellement je n’utilise que la méthode statique, qui est la plus simple.
Le service router
nous fournit la fonction getRoute()
pour appeler une route de façon dynamique à partir de son nom. Le chemin des routes peut-être modifié en cas d’évolution d’un module ou bien raccourci pour rendre l’URL plus propre, mais leur nom restera inchangé. Donc, en utilisant ce service vous assurez le dynamise de vos routes.
Rendez-vous dans le contrôleur TodoController
, puis dans la fonction create()
ajoutez les lignes suvantes :
/* Utilisation de la méthode getRoute() du service router. */ $route = self::router()->getRoute('todo.item.store'); /* Ajout de la route à notre formulaire. */ $form = new FormBuilder(['method' => 'post', 'action' => $route]);
Ici, nous appelons une route simple sans paramètre, mais vous pouvez très bien en rajouter.
Rendez-vous dans le contrôleur TodoController
, puis dans la fonction edit()
ajoutez les lignes suivantes :
/* Utilisation de la mèthode getRoute() avec parmètre du service router. */
$route = self::router()->getRoute('todo.item.update', [ ':id' => $id ]);
/* Ajout de la route à notre formulaire */
$form = new FormBuilder(['method' => 'post', 'action' => $route]);
Maintenant que vous voyez comment utiliser la fonction getRoute()
du service router
je vous laisse modifier chaque appel de route dans le contrôleur TodoController
.
La première étape sera de créer un service qui retournera les données de la “to do list” pour alimenter le contrôleur.
Rendez-vous dans le répertoire TodoModule/Services
et créez un fichier Todo.php
avec le code suivant :
<?php
# modules/TodoModule/Services/Todo.php
namespace TodoModule\Services;
class Todo
{
public function getList()
{
return [
1 => [
'id' => 1,
'title' => 'Item 1',
'height' => 1,
'achieve' => false
],
2 => [
'id' => 2,
'title' => 'Item 2',
'height' => 2,
'achieve' => false
],
3 => [
'id' => 3,
'title' => 'Item 3',
'height' => 3,
'achieve' => false
]
];
}
public function getItem( $id )
{
$data = $this->getList();
return isset($data[$id])
? $data[$id]
: [];
}
}
La seconde étape sera de déclarer notre service. Dans le répertoire TodoModule/Config/
, créez un fichier service.json
avec les lignes suivantes :
{
"todo": {
"class": "TodoModule\\Services\\Todo"
}
}
Pour que votre fichier de déclaration de service soit exécuté, ajoutez l’attribut $pathServices
à votre contrôleur TodoController
et définissez le chemin jusqu’au fichier service.json
.
Vous pouvez vous aider des constantes fournies par SoosyzeCMS :
MODULES_CONTRIBUED
: chemin des modules contributeurs,DS
: alias de la constante DIRECTORY_SEPARATOR
, qui permet de ne pas dépendre du système hôte (Linux ou Mac utilisent le /
et Windows le \
pour séparer le nom des répertoires).<?php
# modules/TodoModule/Controller/TodoController.php
namespace TodoModule\Controller;
define("CONFIG_TODO", MODULES_CONTRIBUED . 'TodoModule' . DS . 'Config' . DS);
class TodoController extends \Soosyze\Controller
{
public function __construct()
{
/* Spécifie le chemin de votre fichier de délcaration de service. */
$this->pathServices = CONFIG_TODO . 'service.json';
$this->pathRoutes = CONFIG_TODO . 'routing.json';
}
/* […] */
}
Vous pouvez définir n’importe quel répertoire pour la déclaration de vos services ou même le nom de votre fichier qui les contient. Je vous invite néanmoins à suivre les conventions de nommage suivantes :
Config
dans chacun de vos modules,services.json
,service-nom_service.json
.Le format JSON est très utilisé dans SoosyzeFramework, je vous conseille alors de vous doter d’un validateur de JSON, vous éviterez plus facilement les erreurs de JSON mal écrits. J’utilise par exemple https://jsonformatter.curiousconcept.com/.
Maintenant que le service est créé et déclaré, vous allez pouvoir l’utiliser sans plus attendre. Dans le contrôleur TodoController
vous allez pouvoir supprimer les deux fonctions privées getItem()
et getList()
, et vous remplacerez leurs utilisations par l’appel au service :
/* Remplacer tous ces appels aux fonctions : */
$this->getList();
$this->getItem( $id );
/* Par les appels aux services : */
self::todo()->getList();
self::todo()->getItem( $id );
Notre service “to do list” est plutôt simple, car il ne nécessite pas d’argument ou de service dans sa construction.
Pour pouvoir tester l’injection, nous allons partir du principe que notre service todo
aura besoin pour se construire des paramètres suivants :
4
),"Lorem ipsum"
),router
),Pour ajouter cette dépendance, rendez-vous dans le fichier service.json
et ajoutez-y les lignes suivantes :
{
"todo": {
"class": "TodoModule\\Services\\Todo",
"arguments": [
4, "Lorem ipsum", "@router", "#settings.local"
]
}
}
Vous noterez que pour différencier une chaîne de caractère d’un service nous utilisons le préfixe @
et pour une valeur de configuration le préfixe #
.
Pour utiliser les préfixe @
et #
comme simples caractères et non comme un appel à l’injection de service ou de valeur de configuration, ajoutez une barre oblique inverse (antislash). Exemple :
\@router
est égal à la chaîne de caractères @router
, et pas le service router
,\#settings.local
est égal à la chaîne de caractères #settings.local
, et pas la valeur de configuration settings.local
.Pour conclure cet exemple, modifiez le service Todo
en créant le constructeur suivant :
<?php
# modules/TodoModule/Services/Todo.php
namespace TodoModule\Services;
class Todo
{
/* Les attributs de classe pour stocker la valeur des attributs du constructeur. */
private $number;
private $str;
private $router;
private $local;
/**
* Notre nombre entier, la chaîne de caractère
* et notre service router seront injectés à la création du service todo.
*/
public function __construct($number, $str, $router, $local)
{
$this->number = $number;
$this->str = $str;
$this->router = $router;
$this->local = $local;
}
/* […] */
}
L’ordre de déclaration de l’injection doit suivre l’ordre d’appel du constructeur.
Il s’agit d’un exemple d’utilisation de l’injection d’argument et de service, vous pouvez ainsi tester la récupération de ces données pour mieux en comprendre le fonctionnement.
Ces attributs n’ont pas d’utilité pour le reste du tutoriel, je vous invite à les supprimer à l’issue de votre test. Mais ne vous en faites pas, nous utiliserons l’injection dans un cas bien plus utile dans le chapitre suivant.
Vous pouvez retrouver les sources de ce chapitre en suivant ce lien.